using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;

using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.IO;

namespace Org.BouncyCastle.Bcpg
{
	/**
	* Basic output stream.
	*/
	public class ArmoredOutputStream
		: BaseOutputStream
	{
		private static readonly byte[] encodingTable =
		{
			(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
			(byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
			(byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
			(byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
			(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
			(byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
			(byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
			(byte)'v',
			(byte)'w', (byte)'x', (byte)'y', (byte)'z',
			(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6',
			(byte)'7', (byte)'8', (byte)'9',
			(byte)'+', (byte)'/'
		};

		/**
		 * encode the input data producing a base 64 encoded byte array.
		 */
		private static void Encode(
			Stream	outStream,
			int[]	data,
			int		len)
		{
			Debug.Assert(len > 0);
			Debug.Assert(len < 4);

			byte[] bs = new byte[4];
			int d1 = data[0];
			bs[0] = encodingTable[(d1 >> 2) & 0x3f];

			switch (len)
			{
				case 1:
				{
					bs[1] = encodingTable[(d1 << 4) & 0x3f];
					bs[2] = (byte)'=';
					bs[3] = (byte)'=';
					break;
				}
				case 2:
				{
					int d2 = data[1];
					bs[1] = encodingTable[((d1 << 4) | (d2 >> 4)) & 0x3f];
					bs[2] = encodingTable[(d2 << 2) & 0x3f];
					bs[3] = (byte)'=';
					break;
				}
				case 3:
				{
					int d2 = data[1];
					int d3 = data[2];
					bs[1] = encodingTable[((d1 << 4) | (d2 >> 4)) & 0x3f];
					bs[2] = encodingTable[((d2 << 2) | (d3 >> 6)) & 0x3f];
					bs[3] = encodingTable[d3 & 0x3f];
					break;
				}
			}

			outStream.Write(bs, 0, bs.Length);
		}

		private readonly Stream outStream;
		private int[]           buf = new int[3];
		private int             bufPtr = 0;
		private Crc24           crc = new Crc24();
		private int             chunkCount = 0;
		private int             lastb;

		private bool            start = true;
		private bool            clearText = false;
		private bool            newLine = false;

		private string          type;

		private static readonly string	nl = Platform.NewLine;
		private static readonly string	headerStart = "-----BEGIN PGP ";
		private static readonly string	headerTail = "-----";
		private static readonly string	footerStart = "-----END PGP ";
		private static readonly string	footerTail = "-----";

		private static readonly string version = "BCPG C# v"
			+ Assembly.GetExecutingAssembly().GetName().Version;

		private readonly IDictionary headers;

		public ArmoredOutputStream(Stream outStream)
		{
			this.outStream = outStream;
			this.headers = Platform.CreateHashtable();
			this.headers["Version"] = version;
		}

		public ArmoredOutputStream(Stream outStream, IDictionary headers)
		{
			this.outStream = outStream;
			this.headers = Platform.CreateHashtable(headers);
			this.headers["Version"] = version;
		}

		/**
		 * Set an additional header entry.
		 *
		 * @param name the name of the header entry.
		 * @param v the value of the header entry.
		 */
		public void SetHeader(
			string name,
			string v)
		{
			headers[name] = v;
		}

		/**
		 * Reset the headers to only contain a Version string.
		 */
		public void ResetHeaders()
		{
			headers.Clear();
			headers["Version"] = version;
		}

		/**
		 * Start a clear text signed message.
		 * @param hashAlgorithm
		 */
		public void BeginClearText(
			HashAlgorithmTag    hashAlgorithm)
		{
			string    hash;

			switch (hashAlgorithm)
			{
			case HashAlgorithmTag.Sha1:
				hash = "SHA1";
				break;
			case HashAlgorithmTag.Sha256:
				hash = "SHA256";
				break;
			case HashAlgorithmTag.Sha384:
				hash = "SHA384";
				break;
			case HashAlgorithmTag.Sha512:
				hash = "SHA512";
				break;
			case HashAlgorithmTag.MD2:
				hash = "MD2";
				break;
			case HashAlgorithmTag.MD5:
				hash = "MD5";
				break;
			case HashAlgorithmTag.RipeMD160:
				hash = "RIPEMD160";
				break;
			default:
				throw new IOException("unknown hash algorithm tag in beginClearText: " + hashAlgorithm);
			}

			DoWrite("-----BEGIN PGP SIGNED MESSAGE-----" + nl);
			DoWrite("Hash: " + hash + nl + nl);

			clearText = true;
			newLine = true;
			lastb = 0;
		}

		public void EndClearText()
		{
			clearText = false;
		}

		public override void WriteByte(
			byte b)
		{
			if (clearText)
			{
				outStream.WriteByte(b);

				if (newLine)
				{
					if (!(b == '\n' && lastb == '\r'))
					{
						newLine = false;
					}
					if (b == '-')
					{
						outStream.WriteByte((byte)' ');
						outStream.WriteByte((byte)'-');      // dash escape
					}
				}
				if (b == '\r' || (b == '\n' && lastb != '\r'))
				{
					newLine = true;
				}
				lastb = b;
				return;
			}

			if (start)
			{
				bool newPacket = (b & 0x40) != 0;

				int tag;
				if (newPacket)
				{
					tag = b & 0x3f;
				}
				else
				{
					tag = (b & 0x3f) >> 2;
				}

				switch ((PacketTag)tag)
				{
				case PacketTag.PublicKey:
					type = "PUBLIC KEY BLOCK";
					break;
				case PacketTag.SecretKey:
					type = "PRIVATE KEY BLOCK";
					break;
				case PacketTag.Signature:
					type = "SIGNATURE";
					break;
				default:
					type = "MESSAGE";
					break;
				}

				DoWrite(headerStart + type + headerTail + nl);
				WriteHeaderEntry("Version", (string) headers["Version"]);

				foreach (DictionaryEntry de in headers)
				{
					string k = (string) de.Key;
					if (k != "Version")
					{
						string v = (string) de.Value;
						WriteHeaderEntry(k, v);
					}
				}

				DoWrite(nl);

				start = false;
			}

			if (bufPtr == 3)
			{
				Encode(outStream, buf, bufPtr);
				bufPtr = 0;
				if ((++chunkCount & 0xf) == 0)
				{
					DoWrite(nl);
				}
			}

			crc.Update(b);
			buf[bufPtr++] = b & 0xff;
		}

		/**
		 * <b>Note</b>: close does nor close the underlying stream. So it is possible to write
		 * multiple objects using armoring to a single stream.
		 */
		public override void Close()
		{
			if (type != null)
			{
				if (bufPtr > 0)
				{
					Encode(outStream, buf, bufPtr);
				}

				DoWrite(nl + '=');

				int crcV = crc.Value;

				buf[0] = ((crcV >> 16) & 0xff);
				buf[1] = ((crcV >> 8) & 0xff);
				buf[2] = (crcV & 0xff);

				Encode(outStream, buf, 3);

				DoWrite(nl);
				DoWrite(footerStart);
				DoWrite(type);
				DoWrite(footerTail);
				DoWrite(nl);

				outStream.Flush();

				type = null;
				start = true;
				base.Close();
			}
		}

		private void WriteHeaderEntry(
			string	name,
			string	v)
		{
			DoWrite(name + ": " + v + nl);
		}

		private void DoWrite(
			string s)
		{
			byte[] bs = Strings.ToAsciiByteArray(s);
			outStream.Write(bs, 0, bs.Length);
		}
	}
}
